Skip to content

feature: add plugin smoke validation workflow#715

Closed
zouyonghe wants to merge 1 commit intoAstrBotDevs:mainfrom
zouyonghe:codex/plugin-smoke-validation
Closed

feature: add plugin smoke validation workflow#715
zouyonghe wants to merge 1 commit intoAstrBotDevs:mainfrom
zouyonghe:codex/plugin-smoke-validation

Conversation

@zouyonghe
Copy link
Copy Markdown
Member

@zouyonghe zouyonghe commented Apr 24, 2026

This change adds an MVP validation path for marketplace plugins so the repository can catch more than malformed plugins.json entries or unreachable repository URLs.

Today the collection only verifies index syntax and repository reachability. That lets broken plugins enter the market even when they are missing metadata.yaml, lack a valid entrypoint, or fail during AstrBot's real plugin loading path. The result is that users can install plugins that immediately fail to import or initialize.

The root cause is that marketplace validation stops before any plugin-structure or AstrBot-runtime checks happen. There was no reusable validator that clones a plugin repository, checks the expected files, and asks AstrBot's PluginManager.load() whether the plugin can actually load.

This PR adds that missing validation layer in two parts. First, it introduces scripts/validate_plugins/run.py, which normalizes GitHub repository URLs, selects target plugins from plugins.json, clones each repository, runs metadata and entrypoint prechecks, and then launches a subprocess worker that places the plugin into a temporary AstrBot root and executes PluginManager.load(specified_dir_name=...). Second, it adds .github/workflows/validate-plugin-smoke.yml, which runs this validator for changed plugin entries on pull requests and supports manual workflow_dispatch runs for targeted checks or limited sweeps.

The validator emits a structured JSON report with stage-specific failures such as repo_url, clone, metadata, entrypoint, load, worker, and timeout, so maintainers can tell why a plugin failed instead of only seeing a generic CI failure. The PR also includes focused unit tests for URL normalization, plugin selection, metadata validation, worker command construction, report aggregation, and worker output parsing.

Validation used for this change:

  • python3 -m unittest tests.test_validate_plugins -v
  • python3 scripts/validate_plugins/run.py --help
  • ruby -e 'require "yaml"; YAML.load_file(".github/workflows/validate-plugin-smoke.yml"); puts "workflow_ok"'

Summary by Sourcery

Introduce an automated smoke validation pipeline for marketplace plugins that clones repositories, performs basic structural checks, attempts runtime loading via AstrBot, and reports structured results in CI.

New Features:

  • Add a plugin validation script that normalizes repository URLs, clones plugin repos, validates metadata and entrypoints, and runs a worker to load plugins through AstrBot.
  • Generate structured JSON reports summarizing per-plugin validation outcomes and overall pass/fail counts.
  • Provide unit tests covering core validator behaviors such as URL normalization, plugin selection, metadata checks, worker command construction, report aggregation, and worker output parsing.

CI:

  • Add a GitHub Actions workflow to run plugin smoke validation on pull requests that touch plugins or validator code and via manual dispatch with configurable plugin selection and AstrBot ref.

@zouyonghe zouyonghe changed the title [codex] add plugin smoke validation workflow feature: add plugin smoke validation workflow Apr 24, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a plugin validation script and a corresponding test suite to automate the verification of AstrBot plugins. The script includes functionality for repository cloning, metadata validation, and a worker-based loading check. The review feedback focuses on enhancing the robustness of the validation logic, specifically by improving the fallback YAML parser to handle trailing comments, ensuring metadata fields like 'name' and 'version' are correctly handled when provided as numeric types, and making the worker output parsing more resilient to extraneous log messages.

if not line or line.startswith("#") or ":" not in line:
continue
key, value = line.split(":", 1)
result[key.strip()] = value.strip().strip("\"'")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback YAML parser does not handle trailing comments. If a line is name: my_plugin # comment, the value will include the comment text, which will likely cause file-not-found errors later when searching for the entrypoint. Consider stripping comments before parsing the value.

Suggested change
result[key.strip()] = value.strip().strip("\"'")
result[key.strip()] = value.split("#", 1)[0].strip().strip("\"'")

missing = [
field
for field in REQUIRED_METADATA_FIELDS
if not isinstance(metadata.get(field), str) or not metadata[field].strip()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check isinstance(metadata.get(field), str) will fail if a field like version is provided as a number in YAML (e.g., version: 1.0) and parsed by PyYAML as a float or int. It is safer to allow numeric types and use str() before calling .strip().

Suggested change
if not isinstance(metadata.get(field), str) or not metadata[field].strip()
if (val := metadata.get(field)) is None or not str(val).strip()

"message": f"missing required metadata fields: {', '.join(missing)}",
}

plugin_name = metadata["name"].strip()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If metadata["name"] is a numeric type (which can happen with some YAML parsers if the name is just numbers), calling .strip() directly will raise an AttributeError. Consider converting it to a string first.

Suggested change
plugin_name = metadata["name"].strip()
plugin_name = str(metadata["name"]).strip()

stdout = completed.stdout.strip()
if stdout:
try:
payload = json.loads(stdout)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

json.loads(stdout) will fail if the worker process or the plugin being loaded prints any logs to stdout before the final JSON result. Since run_worker prints the JSON as the last line, consider parsing only the last line of stdout to be more robust against extra log output.

Suggested change
payload = json.loads(stdout)
payload = json.loads(stdout.splitlines()[-1])

@zouyonghe
Copy link
Copy Markdown
Member Author

Superseded by #716, which is opened directly from zouyonghe:main.

@zouyonghe zouyonghe closed this Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant